{This file is part of CheLive.

CheLive is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar.  If not, see <http://www.gnu.org/licenses/>.}

{Модуль реализует класс камеры}
unit CoreCamera;

interface

uses
	gl, CoreMath;

type
	{Камера
	@member(pos положение)
	@member(rot углы поворота)
	@member(view видовая матрица)
	@member(proj матрица проекций)
	@member(frust уравнения 6 плоскостей ограничивающих видимую геометрию)}
	TCamera = class
		pos, rot: TVec3;
		view, proj, viewProj: TMat4;
		frust: array[0..5] of TVec4;
		
		{Включение камеры (установка матриц и расчет уравнений плоскостей)}
		procedure Enable;
		{Выключение камеры}
		class procedure Disable;
		{Перемещение камеры вперед/назад
		@param(dist расстояние)}
		procedure Move(dist: Single);
		{Перемещение камеры влево/вправо
		@param(dist расстояние)}
		procedure Strafe(dist: Single);

		{Проверка видимости точки
		@param(pos положение)}
		function TestPoint(pos_: TVec3): Boolean;
		{Проверка видимости сферы
		@param(pos положение)
		@param(radius радиус)}
		function TestSphere(pos_: TVec3; radius: Single): Boolean;
		{Проверка видимости паралелепипеда
		@param(min положение первой точки лежащей на диагонали)
		@param(max положение второй точки лежащей на той же диагонали)}
		function TestBox(min, max: TVec3): Boolean;

		constructor Create;
	end;

implementation

constructor TCamera.Create;
begin
	pos := Vec3(0,0,0);
	rot := Vec3(0,0,0);

	Mat4Ident(@view);
	Mat4Persp(@proj, 45, 1, 0.01, 100);
end;

procedure TCamera.Enable;
var
	mat: TMat4;
begin
	glMatrixMode(GL_PROJECTION);
	glPushMatrix;
	glLoadMatrixf(@proj);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix;

	Mat4Trans(@view, -pos.x, -pos.y, -pos.z);
	Mat4RotY(@mat, rot.y);
	Mat4Mult(@view, @mat);
	Mat4RotX(@mat, rot.x);
	Mat4Mult(@view, @mat);
	Mat4RotZ(@mat, rot.z);
	Mat4Mult(@view, @mat);
	glLoadMatrixf(@view);

	viewProj := proj;
	Mat4Mult(@viewProj, @view);
	mat := viewProj;

	frust[0] := Vec4Norm(Vec4(mat.m14 - mat.m11, mat.m24 - mat.m21, mat.m34 - mat.m31, mat.m44 - mat.m41));
	frust[1] := Vec4Norm(Vec4(mat.m14 + mat.m11, mat.m24 + mat.m21, mat.m34 + mat.m31, mat.m44 + mat.m41));
	frust[2] := Vec4Norm(Vec4(mat.m14 + mat.m12, mat.m24 + mat.m22, mat.m34 + mat.m32, mat.m44 + mat.m42));
	frust[3] := Vec4Norm(Vec4(mat.m14 - mat.m12, mat.m24 - mat.m22, mat.m34 - mat.m32, mat.m44 - mat.m42));
	frust[4] := Vec4Norm(Vec4(mat.m14 - mat.m13, mat.m24 - mat.m23, mat.m34 - mat.m33, mat.m44 - mat.m43));
	frust[5] := Vec4Norm(Vec4(mat.m14 + mat.m13, mat.m24 + mat.m23, mat.m34 + mat.m33, mat.m44 + mat.m43));
end;

class procedure TCamera.Disable;
begin
	glMatrixMode(GL_PROJECTION);
	glPopMatrix;
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix;
end;

procedure TCamera.Move(dist: Single);
begin
	pos.x += Sin(rot.y * MATH_TO_RAD) * dist;
	pos.y -= Sin(rot.x * MATH_TO_RAD) * dist;
	pos.z -= Cos(rot.y * MATH_TO_RAD) * dist;
end;

procedure TCamera.Strafe(dist: Single);
begin
	pos.x += Sin((rot.y + 90) * MATH_TO_RAD) * dist;
	pos.z -= Cos((rot.y + 90) * MATH_TO_RAD) * dist;
end;

function TCamera.TestPoint(pos_: TVec3): Boolean;
var
	i: Integer;
begin
	Result := True;

	for i := 0 to 5 do
	begin
		if (frust[i].x * pos_.x + frust[i].y * pos_.y + frust[i].z * pos_.z + frust[i].w) <= 0 then
		begin
			Result := False;
			Exit;
		end;
	end;
end;

function TCamera.TestSphere(pos_: TVec3; radius: Single): Boolean;
var
	i: Integer;
begin
	Result := True;

	for i := 0 to 5 do
	begin
		if (frust[i].x * pos_.x + frust[i].y * pos_.y + frust[i].z * pos_.z + frust[i].w) <= -radius then
		begin
			Result := False;
			Exit;
		end;
	end;
end;

function TCamera.TestBox(min, max: TVec3): Boolean;
var
	i: Integer;
begin
	Result := True;

	for i := 0 to 5 do
	begin
		if (frust[i].x * min.x + frust[i].y * min.y + frust[i].z * min.z + frust[i].w) > 0 then Continue;
		if (frust[i].x * max.x + frust[i].y * min.y + frust[i].z * min.z + frust[i].w) > 0 then Continue;
		if (frust[i].x * max.x + frust[i].y * max.y + frust[i].z * min.z + frust[i].w) > 0 then Continue;
		if (frust[i].x * min.x + frust[i].y * max.y + frust[i].z * min.z + frust[i].w) > 0 then Continue;

		if (frust[i].x * min.x + frust[i].y * min.y + frust[i].z * max.z + frust[i].w) > 0 then Continue;
		if (frust[i].x * max.x + frust[i].y * min.y + frust[i].z * max.z + frust[i].w) > 0 then Continue;
		if (frust[i].x * max.x + frust[i].y * max.y + frust[i].z * max.z + frust[i].w) > 0 then Continue;
		if (frust[i].x * min.x + frust[i].y * max.y + frust[i].z * max.z + frust[i].w) > 0 then Continue;

		Result := False;
		Exit;
	end;
end;

end.
